
/* <<< COPYRIGHT START >>>
 * Copyright 2006-Present OxygenSoftwareLibrary.com
 * Licensed under the GNU Lesser General Public License.
 * http://www.gnu.org/licenses/lgpl.html
 * 
 * @author: Ugorji Dick-Nwoke
 * <<< COPYRIGHT END >>>
 */


package oxygen.tool.classcompatibilityinspector;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.DocErrorReporter;
import com.sun.javadoc.ProgramElementDoc;
import com.sun.javadoc.RootDoc;
import com.sun.javadoc.Tag;

public class CCIDoclet {
  private static final String TAG_EXCLUDE    = "exclude";
  private static final String TAG_DEPRECATED = "deprecated";
  private static final String TAG_PRIVATE = "private_";
  private static final boolean debug = true;
  
  private static List publicApis = new ArrayList();
  private static List internalApis = new ArrayList();
  private static List deprecatedApis = new ArrayList();

  private static int fileIndex = 0;
  private static List prefixes = new ArrayList();
  private static Set publicPackages = new HashSet();
  private static List structures = new ArrayList();
  private static String outputDir = ".";
  
  // Ugorji added a hack here.
  // do not forget to remove the else if for weblogic.xml.stream
  // Jan 25, 2004
  public static boolean start(RootDoc root) throws Exception {
    //boolean au = true; if(au) return true;
    readOptions(root.options());
    System.out.println("starting Ugorji: " + fileIndex);
    ClassDoc[] classes = root.classes();
    for (int i = 0; i < classes.length; i++) {
      ClassDoc cd = classes[i];
      if(!(cd.isPublic() || cd.isProtected() ) ) {
        continue;
      }
      addStructure(cd);
      String pkgname = cd.containingPackage().name().trim();
      Tag[] etags = cd.tags(TAG_EXCLUDE);
      Tag[] dtags = cd.tags(TAG_DEPRECATED);
      if (etags.length > 0 || 
          cd.name().startsWith(TAG_PRIVATE) || 
          isInternalPackage(pkgname) ) {
        addClassApis(cd, internalApis, true);
        //} else if(cd.qualifiedName().startsWith("weblogic.xml.stream")) {
        //addClassApis(cd, deprecatedApis, true);
      } else if(dtags.length > 0) {
        addClassApis(cd, deprecatedApis, true);
      } else {
        addClassApis(cd, publicApis, false);
      }
    }
    writeToFile();
    System.out.println("Done writing");
    return true;
  }

  private static void addStructure(ClassDoc cd) {
    if(cd.isInterface()) {
      ClassDoc[] intfs = cd.interfaces();
      StringBuffer buf = new StringBuffer();
      for(int i = 0; i < intfs.length; i++) {
        buf.append(intfs[i].qualifiedName()).append(",");
      }
      structures.add(new String[] {"interface", cd.qualifiedName(), buf.toString()});
    } else {
      ClassDoc superclass = cd.superclass();
      String superclassname = "";
      if(superclass != null) {
        superclassname = superclass.qualifiedName();
      }
      structures.add(new String[] {"class", cd.qualifiedName(), superclassname});
    }
  }
  
  /**
   * If nothing is contained in the public package file, then
   * We cannot use that list as a determinant of if a class
   * is internal or not, so we return false
   */
  private static boolean isInternalPackage(String pkgname) {
    if(publicPackages.size() == 0)
      return false;
    if(publicPackages.contains(pkgname))
      return false;
    return true;
  }
  
  /**
   * If a class is internal or deprecated, then all APIs in there (methods, variables, etc)
   * are also internal or deprecated.
   * However, if a class is public, some methods or variables might not be.
   * So the last parameter useListPassedForMembers, tells whether to just put all the members
   * into the same bucket as the class, or not
   */
  private static void addClassApis(ClassDoc cd, List list, boolean useListPassedForMembers) throws Exception {
    list.add(cd.qualifiedName());
    
    // now try and add the members
    // // // MethodDoc[] methods = cd.methods();
    performForMemberDocs(cd.methods(), list, useListPassedForMembers);
    performForMemberDocs(cd.constructors(), list, useListPassedForMembers);
    performForMemberDocs(cd.fields(), list, useListPassedForMembers);
  }
  
  private static void performForMemberDocs(ProgramElementDoc[] members, List list, boolean useListPassedForMembers) throws Exception {
    for (int l=0; l < members.length; l++) {      
      if(!(members[l].isPublic() || members[l].isProtected() ) ) {
        continue;
      }
      Tag[] metags=members[l].tags(TAG_EXCLUDE);
      Tag[] mdtags=members[l].tags(TAG_DEPRECATED);
      if(useListPassedForMembers) {
        addMemberApis(members[l], list);
      } else if (metags.length > 0 || members[l].name().startsWith(TAG_PRIVATE)) {
        addMemberApis(members[l], internalApis);
      } else if(mdtags.length > 0) {
        addMemberApis(members[l], deprecatedApis);
      } else {
        addMemberApis(members[l], publicApis);
      }
    }
  }
  
  private static void addMemberApis(ProgramElementDoc member, List list) throws Exception {
    list.add(member.qualifiedName());
  } // for end. method 
    

  private static void writeToFile() throws Exception {
    String fileName = CCITool.FILENAME_PREFIX + fileIndex + ".xml";
    File outfile = new File(outputDir, fileName);
    System.out.println("Writing to file: " + outfile);
    FileWriter fw = new FileWriter(outfile);
    PrintWriter pw = new PrintWriter(fw);
    pw.println("<class-representation>");
    
    pw.println("<class-prefixes>");
    for(Iterator itr = prefixes.iterator(); itr.hasNext(); ) {
      String s = (String)itr.next();
      pw.println(s);
    }
    pw.println("</class-prefixes>");
    pw.println("<public-apis>");
    for(Iterator itr = publicApis.iterator(); itr.hasNext(); ) {
      String s = (String)itr.next();
      pw.println(s);
    }
    pw.println("</public-apis>");
    pw.println("<deprecated-apis>");
    for(Iterator itr = deprecatedApis.iterator(); itr.hasNext(); ) {
      String s = (String)itr.next();
      pw.println(s);
    }
    pw.println("</deprecated-apis>");
    pw.println("<internal-apis>");
    for(Iterator itr = internalApis.iterator(); itr.hasNext(); ) {
      String s = (String)itr.next();
      pw.println(s);
    }
    pw.println("</internal-apis>");
    
    // write structures
    pw.println("<api-structures>");
    for(Iterator itr = structures.iterator(); itr.hasNext(); ) {
      String[] arr = (String[])itr.next();
      if(arr[0].equals("class")) {
        StringBuffer buf = new StringBuffer()
          .append("<class name=\"")
          .append(arr[1])
          .append("\" superclass=\"")
          .append(arr[2])
          .append("\" />");
        pw.println(buf.toString());        
      } else if(arr[0].equals("interface")) {
        StringBuffer buf = new StringBuffer()
          .append("<interface name=\"")
          .append(arr[1])
          .append("\" superinterfaces=\"")
          .append(arr[2])
          .append("\" />");
        pw.println(buf.toString());
      }
    }
    pw.println("</api-structures>");
    
    // finish
    pw.println("</class-representation>");
    
    pw.flush();
    fw.close();
  }
  
  private static void setPublicPackages(String ppfilename) throws Exception {
    FileReader fr = new FileReader(ppfilename);
    BufferedReader br = new BufferedReader(fr);
    String lineread = null;
    while((lineread = br.readLine()) != null) {
      lineread = lineread.trim();
      if(lineread.length() > 0) {
        publicPackages.add(lineread.trim());
      }
    }
    br.close();
  }
  
  public static String readOptions(String[][] options) throws Exception {
    System.out.println("in readOptions");
    for (int i = 0; i < options.length; i++) {
      String[] opt = options[i];
      if (opt[0].equals("-fileindex")) {
        fileIndex = Integer.parseInt(opt[1].trim());
      } else if(opt[0].equals("-prefix")) {
        prefixes.add(opt[1]);
      } else if(opt[0].equals("-publicpackagelist")) {
        setPublicPackages(opt[1]);
      } else if(opt[0].equals("-outputdir")) {
        outputDir = opt[1];
      }
    }
    return "done";
  }

  public static int optionLength(String option) {
    if(option.equals("-fileindex")) {
      return 2;
    } else if(option.equals("-prefix")) {
      return 2;
    } else if(option.equals("-publicpackagelist")) {
      return 2;
    } else if(option.equals("-outputdir")) {
      return 2;
    } 
    return 0; 
  }

  public static boolean validOptions(String options[][], 
                                     DocErrorReporter reporter) {
    return true;
  }
  
}


